package sjm.parse;

import java.util.*;
import lombok.Getter;
/*
 * Copyright (c) 1999 Steven J. Metsker. All Rights Reserved.
 * 
 * Steve Metsker makes no representations or warranties about
 * the fitness of this software for any particular purpose, 
 * including the implied warranty of merchantability.
 */

/**
 * A <code>Parser</code> is an object that recognizes the elements of a
 * language.
 * <p>
 * Each <code>Parser</code> object is either a <code>
 * Terminal</code> or a composition of other parsers. The <code>Terminal</code>
 * class is a subclass of <code>
 * Parser</code>, and is itself a hierarchy of parsers that recognize specific
 * patterns of text. For example, a <code>Word</code> recognizes any word, and a
 * <code>Literal</code> matches a specific string.
 * <p>
 * In addition to <code>Terminal</code>, other subclasses of <code>Parser</code>
 * provide composite parsers, describing sequences, alternations, and
 * repetitions of other parsers. For example, the following <code>
 * Parser</code> objects culminate in a <code>good
 * </code> parser that recognizes a description of good coffee.
 *
 * <blockquote><pre>
 *     Alternation adjective = new Alternation();
 *     adjective.add(new Literal("steaming"));
 *     adjective.add(new Literal("hot"));
 *     Sequence good = new Sequence();
 *     good.add(new Repetition(adjective));
 *     good.add(new Literal("coffee"));
 *     String s = "hot hot steaming hot coffee";
 *     Assembly a = new TokenAssembly(s);
 *     System.out.println(good.bestMatch(a));
 * </pre></blockquote>
 *
 * This prints out:
 *
 * <blockquote><pre>
 *     [hot, hot, steaming, hot, coffee]
 *     hot/hot/steaming/hot/coffee^
 * </pre></blockquote>
 *
 * The parser does not match directly against a string, it matches against an
 * <code>Assembly</code>. The resulting assembly shows its stack, with four
 * words on it, along with its sequence of tokens, and the index at the end of
 * these. In practice, parsers will do some work on an assembly, based on the
 * text they recognize.
 *
 * @author Steven J. Metsker
 *
 * @version 1.0
 */
public abstract class Parser {

    /**
     * a name to identify this parser
     */
    @Getter
    protected String name;

    /**
     * an object that will work on an assembly whenever this parser successfully
     * matches against the assembly
     */
    protected Assembler assembler;

    /**
     * Constructs a nameless parser.
     */
    public Parser() {
    }

    /**
     * Constructs a parser with the given name.
     *
     * @param name A name to be known by. For parsers that are deep composites,
     * a simple name identifying its purpose is useful.
     */
    public Parser(String name) {
        this.name = name;
    }

    /**
     * Accepts a "visitor" which will perform some operation on a parser
     * structure. The book, "Design Patterns", explains the visitor pattern.
     *
     * @param pv the visitor to accept
     */
    public void accept(ParserVisitor pv) {
        accept(pv, new ArrayList());
    }

    /**
     * Accepts a "visitor" along with a collection of previously visited
     * parsers.
     *
     * @param pv the visitor to accept
     *
     * @param visited a collection of previously visited parsers.
     */
    public abstract void accept(ParserVisitor pv, List<Parser> visited);

    /**
     * @param assemblies the collection to look through. These assemblies should
     * already apply assembler.
     *
     * @return the most-matched assembly in a collection.
     */
    public Assembly getMostElementsConsumed(List<Assembly> assemblies) {
        assert assemblies != null && assemblies.size() >= 1;

        Assembly best = assemblies.get(0);
        for (Assembly a : assemblies) {
            if (!a.hasElementsRemain()) {
                // all elements are consumed, return this assembly.
                return a;
            }
            if (a.elementsConsumedNr() > best.elementsConsumedNr()) {
                best = a;
            }
        }
        return best;
    }

    /**
     * @param assembly an assembly to match against
     *
     * @return an assembly with the greatest possible number of elements
     * consumed by this parser.
     */
    public Assembly bestMatch(Assembly assembly) {
        List out = applyAssmebler(Arrays.asList(new Assembly[]{assembly}));
        return getMostElementsConsumed(out);
    }

    /**
     * @param assembly an assembly to match against
     *
     * @return either null, or a completely matched version of the supplied
     * assembly
     */
    public Assembly completeMatch(Assembly assembly) {
        Assembly best = bestMatch(assembly);
        if (best != null && !best.hasElementsRemain()) {
            return best;
        }
        return null;
    }

    /**
     * Create a copy of a vector, cloning each element of the vector.
     *
     * @param assemblies the vector to copy
     *
     * @return a copy of the input vector, cloning each element of the vector
     */
    public static List<Assembly> cloneAssemblies(List<Assembly> assemblies) {
        List out = new ArrayList();
        assemblies.stream().forEach((a) -> {
            out.add(a.clone());
        });
        return out;
    }

    /**
     * Given a list of assemblies, this method matches this parser against all
     * of them, and returns a new list of the assemblies that result from the
     * matches.
     * <p>
     * For example, consider matching the regular expression <code>a*</code>
     * against the string <code>"aaab"</code>. The initial set of states is
     * <code>{^aaab}</code>, where the ^ indicates how far along the assembly
     * is. When <code>a*</code> matches against this initial state, it creates a
     * new set <code>{^aaab, a^aab, aa^ab,
     * aaa^b}</code>.
     *
     * @return a List of assemblies that result from matching against a
     * beginning set of assemblies
     *
     * @param assemblies a list of assemblies to match against
     *
     */
    public abstract List<Assembly> matchAssemblies(List<Assembly> assemblies);

    /**
     * Match this parser against an input state, and then apply this parser's
     * assembler against the resulting state.
     *
     * @param assemblies a list of assemblies to match against
     *
     * @return a List of assemblies that result from matching against a
     * beginning set of assemblies
     */
    public List<Assembly> applyAssmebler(List<Assembly> assemblies) {
        List<Assembly> out = matchAssemblies(assemblies);
        if (assembler != null) {
            out.stream().forEach((assembly) -> {
                assembler.workOn(assembly);
            });
        }
        return out;
    }
    
    /*
     * Create a random expansion for this parser, where a
     * concatenation of the returned collection will be a
     * language element.
     */
    protected abstract List randomExpansion(int maxDepth, int depth);

    /**
     * @param maxDepth
     * 
     * @param separator
     *
     * @return a random element of this parser's language
     */
    public String randomInput(int maxDepth, String separator) {
        StringBuilder buf = new StringBuilder();
        Iterator e = randomExpansion(maxDepth, 0).iterator();
        boolean first = true;
        while (e.hasNext()) {
            if (!first) {
                buf.append(separator);
            }
            buf.append(e.next());
            first = false;
        }
        return buf.toString();
    }

    /**
     * Sets the object that will work on an assembly whenever this parser
     * successfully matches against the assembly.
     *
     * @param assembler the assembler to apply
     *
     * @return Parser this
     */
    public Parser setAssembler(Assembler assembler) {
        this.assembler = assembler;
        return this;
    }

    /**
     * Returns a textual description of this parser.
     *
     * @return String a textual description of this parser, taking care to avoid
     * infinite recursion
     */
    @Override
    public String toString() {
        return toString(new ArrayList());
    }

    /**
     * Returns a textual description of this parser. Parsers can be recursive,
     * so when building a descriptive string, it is important to avoid infinite
     * recursion by keeping track of the objects already described. This method
     * keeps an object from printing twice, and uses
     * <code>unvisitedString</code> which subclasses must implement.
     *
     * @param visited a list of objects already printed
     *
     * @return a textual version of this parser, avoiding recursion
     */
    protected String toString(List<Parser> visited) {
        if (name != null) {
            return name;
        } else if (visited.contains(this)) {
            return "...";
        } else {
            visited.add(this);
            return unvisitedString(visited);
        }
    }

    /**
     * Returns a textual description of this string.
     *
     * @param visited
     *
     * @return
     */
    protected abstract String unvisitedString(List<Parser> visited);
}
